package transcoding

import java.io.BufferedReader
import java.io.FileOutputStream
import java.io.InputStream
import java.io.InputStreamReader
import java.lang.{ Double => JDouble }
import java.lang.{ Long => JLong }
import java.util.UUID
import scala.Array.canBuildFrom
import scala.actors.Actor
import akka.actor.ActorRef
import play.api.Play.current
import play.api.cache.Cache
import play.api.mvc.Request
import play.api.Logger
import play.api.mvc.AnyContent
import java.net.URI
import java.io.IOException

class TranscodedStreamWriter(val outStream: FileOutputStream) extends Actor {
  def act() = {
    loop {
      react {
        case ('Write, data: Array[Byte]) =>
          outStream.write(data)
        case ('Takeover, trasncodedIS: TranscodedInputStream, procIS: InputStream) =>
          def readStream(): Unit = {
            val buffer = new Array[Byte](1024 * 8)
            procIS.read(buffer) match {
              case -1 =>
                //trasncodedIS.closeTrascoding
                outStream.flush
                exit
              case read =>
                val output = new Array[Byte](read)
                System.arraycopy(buffer, 0, output, 0, read)
                outStream.write(output)

                readStream
            }
          }
          readStream
      }
    }
  }
}

class TranscodedStreamConsumer() extends Actor {
  val buffer = new Array[Byte](1024 * 8)
  def act() = {
    loop {
      react {
        case ('Consume, trasncodedIS: TranscodedInputStream, procIS: InputStream) =>
          def readStream(): Unit = {
            procIS.read(buffer) match {
              case -1 =>
                //trasncodedIS.closeTrascoding
                exit
              case read =>
                readStream
            }
          }
          readStream
      }
    }
  }
}

class StreamGobbler extends Actor {
  def act = {
    loop {
      react {
        case (_, stream: InputStream) =>
          Logger.debug("[%s]Gobbling standard error...".format(this.toString))

          var reader: BufferedReader = null
          try {
            reader = new BufferedReader(new InputStreamReader(stream))

            def read: Unit = {
              val line = reader.readLine()
              if (line != null) {
                Logger.debug(line)
                read
              }
            }
            read
          } catch {
            case error => Logger.error("Error while gobbling standard error", error)
          } finally {
            TranscoderUtil.closeQuietly(reader)
            TranscoderUtil.closeQuietly(stream)
          }
        case _ =>
      }
    }
  }
}

object TranscoderUtil {
  import java.lang.{ Long => JLong }
  import java.lang.{ Double => JDouble }
  import java.lang.{ Integer => JInt }

  private def offsetSeconds(params: Map[String, Seq[String]]) = {
    val startParam = params.getOrElse("start", Seq("0")).fold("")((a, b) => a + b);
    try { JDouble.parseDouble(startParam) }
    catch { case e => Logger.warn("Could not parse start param: %s".format(startParam), e); 0.0 }
  }

  private def offsetFromSecondsToBytes(params: Map[String, Seq[String]])(implicit mf: MediaFile) = {
    (offsetSeconds(params) * (mf.bitRate.get / 8)).round
  }

  private def bitrate(params: Map[String, Seq[String]])(implicit mf: MediaFile) = {
    if (params.contains("maxBitRate")) {
      try {
        val bitrate = params.getOrElse("maxBitRate", Seq("")).fold("")((a, b) => a + b)
        Some(JInt.parseInt(bitrate))
      } catch {
        case _ => None
      }
    } else None
  }

  def size(bitrate: Option[Int])(implicit mf: MediaFile) = {

    var scalingRatio = 1.0
    bitrate match {
      case None =>
      case Some(br) =>
        if (mf.bitRate.getOrElse(0) > 0)
          scalingRatio = scala.math.sqrt(br / mf.bitRate.get)
        else Logger.warn("Media File contained negative bitrate")

        if (scalingRatio < 1.0)
          Logger.debug("Scaling video dimension by %f becasue max bitrate is %s".format(scalingRatio, br))
    }

    "%dx%d".format(even((mf.videoDimensions._1 * scalingRatio).round), even((mf.videoDimensions._2 * scalingRatio).round))
  }

  private def even(i: Long) = {
    i + (i % 2)
  }

  def apply(filePath: String, params: Map[String, Seq[String]], reqID: String, flag: Boolean) = {

  }

  def apply(mfm: MediaFileManager, request: Request[AnyContent], reqID: String): Option[(InputStream, MediaFile)] = {

    implicit val mediaFile = mfm.mainMediaFile
    val params = request.queryString
    val skipTo = offsetSeconds(params).round
    val maxBitRate = bitrate(params)
    val player = params.getOrElse("playerID", Seq()).fold("")((a, b) => a + b)
    
    val pi = new ProcessInfo(UUID.randomUUID.toString, request.host, mfm)

    Logger.debug("Process Info " + pi)

    TranscodeProcessManager.startProcessAndContructStream(pi, mfm, maxBitRate, if(player == "flash") Some("mp4") else None, skipTo)
    	.map(p => (p._1, p._2))
  }

  private def markAsActive(stream: InputStream, reqID: String) = {
    Logger.debug("markAsActive: " + reqID)
    Cache.set(reqID, stream, 3600 * 12)

    stream
  }

  def stopIfActive(reqID: String) = {
    //FIXME: need some sort of janitor process for this
    Cache.get(reqID) match {
      case Some(stream) =>
        Logger.error("Found dangling stream! Closing...")
        val mediaStream = stream.asInstanceOf[InputStream]
        //closeQuietly(mediaStream)
        Cache.set(reqID, null, 0) //remove
      case None =>
    }
  }

  def closeQuietly(in: java.io.Closeable) {
    try {
      Logger.trace("Quietly closing %s".format(in))
      in.close()
    } catch {
      case e => Logger.warn("Error while quietly closing", e)
    }
  }

  def forceSkip(fileStream: InputStream, start: Long): InputStream = {
    var toSkip = start
    var tries = 0
    Logger.debug("Skipping %d bytes".format(start))
    while (toSkip > 0) {
      val skipped = fileStream.skip(start)
      Logger.debug("Skipped %d bytes".format(skipped))
      toSkip -= skipped
      if (tries >= 9) {
        Logger.warn("After 10 tries it is not possible to fully skip. Skipped %d".format(start - toSkip))
        return fileStream
      }
      tries += 1
    }
    fileStream
  }

  def size(params: Map[String, Seq[String]], bitrate: Double)(implicit mf: MediaFile) = {
    var scalingRatio = 1.0

    if (mf.bitRate.getOrElse(0) > 0)
      scalingRatio = scala.math.sqrt(bitrate / mf.bitRate.get)
    else Logger.warn("Media File contained negative bitrate")

    if (scalingRatio < 1.0)
      Logger.debug("Scaling video dimension by %f becasue max bitrate is %s".format(scalingRatio, bitrate))

    "%dx%d".format(even((mf.videoDimensions._1 * scalingRatio).round), even((mf.videoDimensions._2 * scalingRatio).round))
  }

  def encodeSpaces(str: String) = { str.replaceAll(" ", "%20%") }
  def decodeSpaces(str: String) = { str.replaceAll("%20%", " ") }
  def ensureDirExists(filepath: String) = {
    val file = new java.io.File(filepath)
    file.getParentFile().mkdirs()
    
    filepath
  }
}
